/*
    White Elephant - A Simple Cross Platform Line Testing App For Animations.
    Copyright (C) 2012  O. Bahri Gordebak
    
    Contact author at <gordebak@gmail.com>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "sizedialog.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QTimer>
#include <QFileDialog>
#include <QLabel>
#include <QSpinBox>
#include <QBitmap>
#include <QShortcut>
#include <QTranslator>

using namespace std;

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    QTranslator translator;
    //Loads the appropriate translation
    //according to the system locale
    translator.load(QLocale::system(), "whiteelephant", "_", ":locale", ".qm");
    qApp->installTranslator(&translator); 
    ui->setupUi(this);
    //this the first run of program
    isFirstRun = true;
    //create text on the screen
    frameLabel = new QLabel;
    frameLabel->setText(tr("     Frame: "));
    layerLabel = new QLabel;
    layerLabel->setText(tr("     Layer: "));
    frameNumberLabel = new QLabel;
    layerNumberLabel = new QLabel;
    frameNumberLabel->setText("0");
    layerNumberLabel->setText("0");
    fpsLabel = new QLabel;
    fpsLabel->setText(tr("     FPS: "));
    //create fps spinbox
    fps = new QSpinBox;
    fps->setMaximum(30);
    fps->setMinimum(1);
    fps->setValue(12);
    //create empty labels for some space between
    //widgets
    emptyLabel = new QLabel;
    emptyLabel->setText("         ");
    emptyLabel2 = new QLabel;
    emptyLabel2->setText("         ");
    //add them to the toolbox in order
    ui->statusToolBar->addWidget(emptyLabel);
    ui->statusToolBar->addWidget(fpsLabel);
    ui->statusToolBar->addWidget(fps);
    ui->statusToolBar->addWidget(frameLabel);
    ui->statusToolBar->addWidget(frameNumberLabel);
    ui->statusToolBar->addWidget(layerLabel);
    ui->statusToolBar->addWidget(layerNumberLabel);
    ui->statusToolBar->addWidget(emptyLabel2);
    //timer for playing the animation
    timer = new QTimer(this);
    sizeDialog = new SizeDialog(this);

    //when the program first started
    //disable the buttons
    ui->actionAddEmpty->setDisabled(true);
    ui->actionAddLayer->setDisabled(true);
    ui->actionDown->setDisabled(true);
    ui->actionNext_Frame->setDisabled(true);
    ui->actionPlay->setDisabled(true);
    ui->actionPrevious_Frame->setDisabled(true);
    ui->actionRemoveLayer->setDisabled(true);
    ui->actionRender->setDisabled(true);
    ui->actionToggleLoop->setDisabled(true);
    ui->actionUp->setDisabled(true);
    ui->actionMultiply->setDisabled(true);
    ui->actionFirst->setDisabled(true);
    ui->actionLast->setDisabled(true);
    ui->actionTopLayer->setDisabled(true);
    ui->actionBottomLayer->setDisabled(true);
    ui->actionDelete->setDisabled(true);
    ui->actionAddImages->setDisabled(true);
    ui->actionHide->setDisabled(true);
    
    //set the shortcuts to the actions
    ui->actionPrevious_Frame->setShortcut(Qt::Key_Left);
    ui->actionNext_Frame->setShortcut(Qt::Key_Right);
    ui->actionUp->setShortcut(Qt::Key_Up);
    ui->actionDown->setShortcut(Qt::Key_Down);
    ui->actionNew->setShortcut(Qt::Key_N);
    ui->actionPlay->setShortcut(Qt::Key_Space);
    ui->actionAddEmpty->setShortcut(Qt::Key_E);
    ui->actionRender->setShortcut(Qt::Key_R);
    ui->actionToggleLoop->setShortcut(Qt::Key_L);
    ui->actionAddLayer->setShortcut(Qt::Key_Plus);
    ui->actionRemoveLayer->setShortcut(Qt::Key_Minus);
    ui->actionMultiply->setShortcut(Qt::Key_W);
    ui->actionTopLayer->setShortcut(Qt::CTRL + Qt::Key_Up);
    ui->actionBottomLayer->setShortcut(Qt::CTRL + Qt::Key_Down);
    ui->actionFirst->setShortcut(Qt::CTRL + Qt::Key_Left);
    ui->actionLast->setShortcut(Qt::CTRL + Qt::Key_Right);
    ui->actionDelete->setShortcut(Qt::Key_Delete);
    ui->actionAddImages->setShortcut(Qt::Key_A);
    ui->actionHide->setShortcut(Qt::Key_H);

    //timer starts when play clicked, and when the timer times out play the next frame
    connect(timer, SIGNAL(timeout()), this, SLOT(playNextFrame()));
    connect(ui->actionPlay, SIGNAL(triggered()), this, SLOT(play()));
    connect(ui->actionAddImages, SIGNAL(triggered()), this, SLOT(addFiles()));
    connect(ui->actionPrevious_Frame, SIGNAL(triggered()), this, SLOT(showPreviousFrame()));
    connect(ui->actionNext_Frame, SIGNAL(triggered()), this, SLOT(showNextFrame()));
    connect(ui->actionNew, SIGNAL(triggered()), this, SLOT(newAnim()));
    connect(ui->actionRender, SIGNAL(triggered()), this, SLOT(render()));
    connect(ui->actionToggleLoop, SIGNAL(triggered()), this, SLOT(toggleLoop()));
    connect(ui->actionAddLayer, SIGNAL(triggered()), this, SLOT(addLayer()));
    connect(ui->actionRemoveLayer, SIGNAL(triggered()), this, SLOT(removeLayer()));
    connect(ui->actionUp, SIGNAL(triggered()), this, SLOT(layerUp()));
    connect(ui->actionDown, SIGNAL(triggered()), this, SLOT(layerDown()));
    connect(ui->actionAddEmpty, SIGNAL(triggered()), this, SLOT(addEmptyFrame()));
    connect(ui->actionMultiply, SIGNAL(triggered()), this, SLOT(makeWhiteTransparent()));
    connect(ui->actionFirst, SIGNAL(triggered()), this, SLOT(showFirstFrame()));
    connect(ui->actionLast, SIGNAL(triggered()), this, SLOT(showLastFrame()));
    connect(ui->actionTopLayer, SIGNAL(triggered()), this, SLOT(layerTop()));
    connect(ui->actionBottomLayer, SIGNAL(triggered()), this, SLOT(layerBottom()));
    connect(ui->actionDelete, SIGNAL(triggered()), this, SLOT(removeFrame()));
    connect(ui->actionHide, SIGNAL(triggered()), this, SLOT(hideLayer()));
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::addImages(QStringList fileNames)
{
    fileNames.sort();
    QPixmap tempPixmap;
    //add all images from the last frame of current layer
    QList<QPixmap> tmpList = allImages.at(currentLayer - 1);
    for (int i = 0; i < fileNames.size(); i++)
    {
        tempPixmap.load(fileNames.at(i));
        tmpList.append(tempPixmap);
    }
    //remove the empty pixmap added when the new layer created
    if(isEmpty.at(currentLayer - 1))
    {
        tmpList.removeFirst();
        isEmpty.replace(currentLayer - 1, false);
    }
    //insert the current layer's images to the list of all images
    allImages.replace(currentLayer - 1, tmpList);
    //frame count equals to the size of longest layer
    calculateFrameCount(tmpList);
    tmpList.clear();
    drawFrame();
}

void MainWindow::addLayer()
{
    layerCount++;
    currentLayer = layerCount;
    QList<QPixmap> tempList;
    //create an empty pixmap to add to the new layer
    QPixmap pixmap = QPixmap(w, h);
    pixmap.fill(QColor(0,0,0,0));
    //add the pixmap to an empty list
    tempList.append(pixmap);
    //add the empty list as a last layer
    allImages.append(tempList);
    //this layer is empty
    isEmpty.append(true);
    //this layer isn't hidden
    isHidden.append(false);
    //white isn't transparent on new layer
    isMultiply.append(false);
    updateLayers();
}

void MainWindow::makeWhiteTransparent()
{
    //if white is not transparent on this layer
    //make it transparent
    if (!isMultiply.at(currentLayer - 1))
        isMultiply.replace(currentLayer - 1, true);
    //if it is
    //make it opaque
    else
        isMultiply.replace(currentLayer - 1, false);
    updateLayers();
    drawFrame();
}

void MainWindow::hideLayer()
{
    //if layer is not hidden
    //hide it
    if (!isHidden.at(currentLayer - 1))
        isHidden.replace(currentLayer - 1, true);
    //if it is
    //show it
    else
        isHidden.replace(currentLayer - 1, false);
    updateLayers();
    drawFrame();
}

void MainWindow::updateLayers()
{
    //update the QLabel showing the layer number
    layerNumberStr.setNum(currentLayer);
    layerNumberLabel->setText(layerNumberStr);
    //change the icon of the "Make White Trasnparent" button
    //according to selected or not
    if(isMultiply.at(currentLayer - 1))
         ui->actionMultiply->setIcon(QIcon(":images/multiplySelected.svg"));
    else
         ui->actionMultiply->setIcon(QIcon(":images/multiply.svg"));
    //change the icon of the "Hide" button
    //according to selected or not
    if(isHidden.at(currentLayer - 1))
         ui->actionHide->setIcon(QIcon(":images/hideSelected.svg"));
    else
         ui->actionHide->setIcon(QIcon(":images/hide.svg"));
}

void MainWindow::removeLayer()
{
    layerCount--;
    //remove the list of current layer form all images
    allImages.removeAt(currentLayer-1);
    //remove if this layer is empty or not
    isEmpty.removeAt(currentLayer - 1);
    //remove if white is transparent on this layer or not
    isMultiply.removeAt(currentLayer - 1);
    //remove if layer is hidden or not
    isHidden.removeAt(currentLayer - 1);
    //go to first layer
    currentLayer = 1;
    //if there are no more layers, add one
    if(layerCount < 1)
        addLayer();
    drawFrame();
    updateLayers();
}

void MainWindow::layerUp()
{
    currentLayer++;
    if (currentLayer > layerCount)
       currentLayer = layerCount;
    updateLayers();
    drawFrame();
}

void MainWindow::layerDown()
{
    currentLayer--;
    if (currentLayer < 1)
        currentLayer = 1;
    updateLayers();
    drawFrame();
}

void MainWindow::layerTop()
{
    currentLayer = layerCount;
    updateLayers();
    drawFrame();
}

void MainWindow::layerBottom()
{
    currentLayer = 1;
    updateLayers();
    drawFrame();
}

void MainWindow::play()
{
    if(!isPlaying)
    {
        //if not playing
        //make the loop button icon pause and...
        isPlaying = true;
        ui->actionPlay->setIcon(QIcon(":images/pause.svg"));
        //...play
        //wait nearly 40ms between every frame at 24 fps
        for (int i = 0; i < frameCount; i++)
            //timer is connected to playNextFrame() slot
            //when timer times out next frame shows
            timer->start(40 * int(40 / (3.333333 * fps->value())));
    }
    else
    {
        //if playing
        //make the loop button icon play and...
        isPlaying = false;
        ui->actionPlay->setIcon(QIcon(":images/play.svg"));
        //...pause
        timer->stop();
    }
}

void MainWindow::playNextFrame()
{
    frame++;
    if (frame > frameCount)
    {
        frame = 1;
        //if looping isn't selected stop the animation
        //at the last frame
        if(!isLooping)
        {
            isPlaying = false;
            timer->stop();
            //when the animation stopped
            //change the pause icon to play icon
            ui->actionPlay->setIcon(QIcon(":images/play.svg"));
        }
    }
    drawFrame();
}

void MainWindow::addFiles()
{
    fileNames = QFileDialog::getOpenFileNames(
                    this,
                    "Select one or more files to open",
                    "/home",
                    "Images (*.png *.xpm *.jpg)");
    if (!fileNames.isEmpty())
        addImages(fileNames);
}

void MainWindow::showNextFrame()
{
    frame++;
    if(frame > frameCount)
        frame = 1;
    drawFrame();
}

void MainWindow::showPreviousFrame()
{
    frame--;
    if(frame < 1)
        frame = frameCount;
    drawFrame();
}

void MainWindow::showFirstFrame()
{
    frame = 1;
    drawFrame();
}

void MainWindow::showLastFrame()
{
    frame = frameCount;
    drawFrame();
}

void MainWindow::addEmptyFrame()
{
    //if layer is not empty
    if(!isEmpty.at(currentLayer - 1))
    {
    //create an empty pixmap with the size of the document
    QList<QPixmap> tmpList = allImages.at(currentLayer - 1);
    QPixmap pixmap = QPixmap(w, h);
    pixmap.fill(QColor(0,0,0,0));
    //add it to the layer at the end
    tmpList.append(pixmap);
    allImages.replace(currentLayer - 1, tmpList);
    }
    //make it afull layer whether image added or not
    isEmpty.replace(currentLayer - 1, false);
    drawFrame();
}

void MainWindow::removeFrame()
{
    if(!isEmpty.at(currentLayer - 1))
    {
        QList<QPixmap> tmpList = allImages.at(currentLayer - 1);
        //remove the current frame
        tmpList.removeAt(frame - 1);
        //if there are no more frames,
        //add an empty pixmap and mark layer as empty
        if(tmpList.size() == 0)
        {
            QPixmap pixmap = QPixmap(w, h);
            pixmap.fill(QColor(0,0,0,0));
            //add it to the layer at the end
            tmpList.append(pixmap);
            //update the layer information in the list
            isEmpty.replace(currentLayer - 1, true);
        }
        //update the layer
        allImages.replace(currentLayer - 1, tmpList);
        //if frame count is bigger than the current layer's frame count
        //decrease frame count
        if(frameCount > tmpList.size())
            frameCount--;
        //then calculate the frame count again
        //this is a bit of a dirty hack
        for(int i = 0; i < allImages.size(); i++)
        {
            QList<QPixmap> tempList = allImages.at(i);
            calculateFrameCount(tempList);
        }
        //if current frame is after the last frame of current layer
        //make it last frame
        if(frame > tmpList.size())
            frame--;
        drawFrame();
    }
}

    //reset all the values and add a first layer
void MainWindow::newAnim()
{
    if (sizeDialog->exec() == QDialog::Accepted)
    {
        if (!isFirstRun)
       {
            //if this is not program's first run
            //delete the created views and scenes
            delete scene;
            delete graphicsView;
            delete thumbnailScene;
            delete frameThumbnail;
        }
        //and now this is not first run anymore.
        isFirstRun = false;
        //enable the buttons
        ui->actionAddEmpty->setDisabled(false);
        ui->actionAddLayer->setDisabled(false);
        ui->actionDown->setDisabled(false);
        ui->actionNext_Frame->setDisabled(false);
        ui->actionPlay->setDisabled(false);
        ui->actionPrevious_Frame->setDisabled(false);
        ui->actionRemoveLayer->setDisabled(false);
        ui->actionRender->setDisabled(false);
        ui->actionToggleLoop->setDisabled(false);
        ui->actionUp->setDisabled(false);
        ui->actionMultiply->setDisabled(false);
        ui->actionFirst->setDisabled(false);
        ui->actionLast->setDisabled(false);
        ui->actionTopLayer->setDisabled(false);
        ui->actionBottomLayer->setDisabled(false);
        ui->actionDelete->setDisabled(false);
        ui->actionAddImages->setDisabled(false);
        ui->actionHide->setDisabled(false);

        //read the width and height of document
        //that were written by the sizeDialog
        settings = new QSettings;

        w = settings->value("Width", 768).toInt();
        h = settings->value("Height", 576).toInt();
        //create the document and thumbnail view
        //and add them to the window
        thumbnailScene = new QGraphicsScene(this);
        thumbnailScene->setSceneRect(0, 0, int(w / 10), int(h / 10));
        frameThumbnail = new QGraphicsView(thumbnailScene);
        frameThumbnail->setSceneRect(0, 0, int(w / 10), int(h / 10));
        frameThumbnail->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        frameThumbnail->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        frameThumbnail->setFixedSize(int(w / 10), int(h / 10));
        ui->statusToolBar->addWidget(frameThumbnail);
        scene = new QGraphicsScene(this);
        scene->setSceneRect(0, 0, w, h);
        graphicsView = new QGraphicsView(scene);
        graphicsView->setSceneRect(0, 0, w, h);
        graphicsView->setRenderHint(QPainter::Antialiasing);
        graphicsView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        graphicsView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
        graphicsView->setFixedSize(w, h);
        ui->viewLayout->addWidget(graphicsView);
        //resize the window according to the document size
        resize(w + 100, h + 100);
        //minimum size of the window is this
        //I don't like scroll bars
        setMinimumSize(w + 100, h + 100);
        //initiate the variables and lists.
        frame = 1;
        frameCount = 1;
        layerCount = 0;
        currentLayer = 0;
        allImages.clear();
        scene->clear();
        thumbnailScene->clear();
        isLooping = false;
        isPlaying = false;
        isEmpty.clear();
        addLayer();
        updateLayers();
    }
}

void MainWindow::render()
{
    //frameNumberStr is string of current frame's number
    QString frameNumberStr;
    QString imageFile;
    //choose a directory to render in
    QString dir = QFileDialog::getExistingDirectory(this, tr("Choose Directory"),
                                                 "/home",
                                                 QFileDialog::ShowDirsOnly
                                                 | QFileDialog::DontResolveSymlinks);
    if (!dir.isEmpty())
    {

        //run drawFrame() function for every frame
        //and save the frame
        for (int i = 0; i < frameCount; i++)
        {
            scene->clear();
            frame = i + 1;
            drawFrame();
            //filenames are frame001.png, frame002.png etc.
            std::stringstream ss;
            ss << std::setw(3) << std::setfill('0') << (i + 1);
            frameNumberStr = QString(ss.str().c_str());
            imageFile = dir + "/frame" + frameNumberStr +".png";
            scene->clearSelection();
            //make an image of current frame as it is seen
            QPixmap pixmap(QPixmap::grabWidget(graphicsView, scene->sceneRect().toRect()));

            QPainter painter(&pixmap);
            scene->render(&painter);
            //save the frame
            pixmap.save(imageFile);
        }
    }
}

void MainWindow::toggleLoop()
{
    //if loop button is selected make it unselected
    //and stop looping
    //if loop button is unselected make it selected
    //and start looping
    if (isLooping)
    {
        isLooping = false;
        ui->actionToggleLoop->setIcon(QIcon(":images/loop.svg"));
    }
    else
    {
        isLooping = true;
        ui->actionToggleLoop->setIcon(QIcon(":images/loopSelected.svg"));   
    }
}

void MainWindow::drawFrame()
{
    //draw the frame layer by layer
    scene->clear();
    thumbnailScene->clear();
    QPixmap previewPixmap;
    int emptyLayers = 0;
    QList<QPixmap> previewTmpList = allImages.at(currentLayer - 1);
    //if current frame is smaller than current layer's frame count
    //add the scaled frame to the thumbnail view.
    if(frame <= previewTmpList.size())
         previewPixmap = previewTmpList.at(frame - 1).scaledToWidth(thumbnailScene->width());
    //if current frame is bigger than current layer's frame count
    //add the scaled last frame in layer to the thumbnail view.
    else
         previewPixmap = previewTmpList.last().scaledToWidth(thumbnailScene->width());
    //for all the layers
    for (int i = 0; i < allImages.size(); i++)
    {
        QList<QPixmap> tempList = allImages.at(i);
        QPixmap tmpPixmap;
        //if layer isn't hidden
        if(!isHidden.at(i))
        {
            //if current frame is smaller than the current layer's frame count
            //add the frame
            if(frame <= tempList.size())
                tmpPixmap = tempList.at(frame-1);
            //if current frame is bigger than the current layer's frame count
            //add the last frame in layer
            else
                tmpPixmap = tempList.last();
            //if "make white transparent" is clicked on this layer
            //make white transparent on the current frame on current layer
            if(isMultiply.at(i))
            {
                QBitmap bitmap(tmpPixmap.createMaskFromColor(Qt::white, Qt::MaskInColor));
                tmpPixmap.setMask(bitmap);
            }
            //finally show the images
            scene->addPixmap(tmpPixmap);
        }
        //show the image on thumbnail
        //even if the layer is hidden
        thumbnailScene->addPixmap(previewPixmap);
        calculateFrameCount(tempList);
        //count the empty layers
        if (isEmpty.at(i))
            emptyLayers++;
    }
    //update the QLabel showing current frame number
    if (emptyLayers < layerCount)
        frameNumberStr.setNum(frame);
    else
        frameNumberStr.setNum(frame - 1);
    frameNumberLabel->setText(frameNumberStr);
}

void MainWindow::calculateFrameCount(QList<QPixmap> tmpList)
{
    //the frame count equals to the frame count of the longest layer
    if (frameCount < tmpList.size())
        frameCount = tmpList.size();
}
